﻿using System.Collections.Generic;
using System;
using NTree=NGenerics.DataStructures.Trees;
using NGen=NGenerics;
using NGenGen= NGenerics.DataStructures.General;
using System.Reflection;

namespace Layers.History
{

    public class TWindow
    {
            public long Begin;
            public long End;

            public long Length { get { return End - Begin; } }

            public int Count;
        
            public object Data;

            public int GetTimeOrder(long Time)
            {
                if (Time < Begin)
                    return -1;
                else if (Time > End)
                    return 1;
                else return 0;
            }

            public class BeginComparer : IComparer<TWindow>
            {
                public int  Compare(TWindow x, TWindow y)
                {
 	                return (int)(x.Begin-y.Begin);
                }
            }

            public class TimeComparable : IComparable<TWindow>
            {
                public long Time;

                public TimeComparable(long time)
                {
                    Time = time;
                }

                public int CompareTo(TWindow other)
                {
                    return other.GetTimeOrder(Time);
                }
            }
    }
   
    public static class TWindowFlags
    {
        public static int ReadOnly = 0x01;
        public static int NodeGrown = 0x02;
        public static int NodeCreated = 0x04;
        public static int NodeOverwrite = 0x08;
        public static int CurrentDirty = 0x10;
        public static int NodeChanged = 0x20;
    }

    public delegate void TWindowDelegate(TWindow wnd);

    public class TWindowCollection : IEnumerable<TWindow>
    {
        public long WindowDefaultLength { get; set; }

        public long MaximumWindowLength { get { return WindowDefaultLength; } }

        protected NTree.RedBlackTree<long, TWindow> _impl = new NTree.RedBlackTree<long, TWindow>();

        public virtual NTree.BinaryTree<NGenGen.Association<long, TWindow>> FindNode(long time, SearchOption option)
        {
            return _impl.Search<long,TWindow>(new TWindow.TimeComparable(time), option);
        }

        public virtual TWindow Find(long time, SearchOption option)
        {
            return this.FindNode(time, option).Data.Value;
        }



        internal NTree.BinaryTree<NGenGen.Association<long, TWindow>> GetNextNode(NTree.BinaryTree<NGenGen.Association<long, TWindow>> current)
        {
            return current.Next;
        }

        internal NTree.BinaryTree<NGenGen.Association<long, TWindow>> GetPrevNode(NTree.BinaryTree<NGenGen.Association<long, TWindow>> current)
        {
            return current.Prev;
        }

        internal NTree.BinaryTree<NGenGen.Association<long, TWindow>> GetFirstNode()
        {
            return _impl.Count>0?NTree.RedBlackTree<long, TWindow>.FindMaximumNode(_impl.RootNode):null;
        }

        internal NTree.BinaryTree<NGenGen.Association<long, TWindow>> InsertNode(TWindow wnd)
        {
            return _impl.AddNode(wnd.Begin, wnd);
        }

        internal TWindow GetNodeWindow(NTree.BinaryTree<NGenGen.Association<long, TWindow>> current)
        {
            return current.Data.Value;
        }

        internal virtual bool GrowNode(long time, ref NTree.BinaryTree<NGenGen.Association<long, TWindow>> current, ref SeekPos pos, ref int flags)
        {
            NTree.BinaryTree<NGenGen.Association<long, TWindow>> next;

            next = GetNextNode(current);

            long nextBegin = long.MaxValue;

            if (next == null || time<(nextBegin=GetNodeWindow(next).Begin)) // we're last or have space till next
            {
                TWindow wnd = GetNodeWindow(current);
                if (time - wnd.Begin < MaximumWindowLength) // may grow current
                {
                    wnd.End = time;
                    flags |= TWindowFlags.NodeGrown;
                    return true;
                }
                else // need create new since current window too long
                {
                    return NewNode(time, time, out current, ref pos, ref flags);
                }
            }else // overwrite pending
            {
                return OverwriteNode(time, ref current, next, ref pos, ref flags);
            }
        }

        internal bool OverwriteNode(long time, ref NTree.BinaryTree<NGenGen.Association<long, TWindow>> current,
            NTree.BinaryTree<NGenGen.Association<long, TWindow>> ovr , ref SeekPos pos, ref int flags)
        {
            // by default we destroy ovr and start new node
            ovr.Parent.Remove(ovr);
            flags |= TWindowFlags.NodeOverwrite;
            return NewNode(time, time, out current, ref pos, ref flags);
        }

        internal bool NewNode(long begin, long end, 
            out NTree.BinaryTree<NGenGen.Association<long, TWindow>> current, 
            ref SeekPos pos, ref int flags )
        {
            TWindow wnd = new TWindow();
            wnd.Begin = begin;
            wnd.End = end;
            current = _impl.AddNode(begin, wnd);
            pos = SeekPos.Current;
            flags |= TWindowFlags.NodeCreated;
            flags |= TWindowFlags.NodeChanged;
            return true;
        }

        internal virtual bool ForwardNode(ref long time, ref NTree.BinaryTree<NGenGen.Association<long, TWindow>> current, ref SeekPos pos, ref int flags)
        {
            flags &= ~(TWindowFlags.NodeCreated | TWindowFlags.NodeGrown | TWindowFlags.NodeOverwrite|TWindowFlags.NodeChanged);

            if (current == null)
            {
                if (pos != SeekPos.BeforeFirst)
                    throw new InvalidOperationException("current==null and pos==" + pos + " <> BeforeStart");
                if ((flags & TWindowFlags.ReadOnly) != 0) // readonly
                {
                    return FirstNode(out time, out current, ref pos, ref flags);
                }
                else
                {
                    return NewNode(time, time, out current, ref pos, ref flags);
                }
            }else // current!=null
            {
                if (pos != SeekPos.Current)
                    throw new InvalidOperationException("current!=null and pos==" + pos + " <> Current");

                TWindow w = GetNodeWindow(current);
                int order = w.GetTimeOrder(time);
                if (order > 0)  // moved right from current
                {

                    if ((flags & TWindowFlags.ReadOnly) == 0) // Write mode
                    {
                        return GrowNode(time, ref current, ref pos, ref flags);
                    }
                    else // read-only - try find next
                    {
                        NTree.BinaryTree<NGenGen.Association<long, TWindow>> next;

                        // check next (quick)
                        next = GetNextNode(current);
                        if (null != next && GetNodeWindow(next).GetTimeOrder(time) == 0)
                        {
                            // catch next window if OK
                            current = next;
                            flags |= TWindowFlags.NodeChanged;
                            return true;
                        }
                        // find next using B-tree
                        next = FindNode(time, SearchOption.Equal);
                        if (null != next)
                        {
                            current = next;
                            flags |= TWindowFlags.NodeChanged;
                            return true;
                        }
                        current = null;
                        pos = SeekPos.AfterLast;
                        return false;
                    }
                }
                else if (order < 0)
                    throw new InvalidOperationException("moving left not supported");
                else // order==0
                    return true;
            }
        }

        internal virtual bool FirstNode(out long time, out NTree.BinaryTree<NGenGen.Association<long, TWindow>> current, ref SeekPos pos, ref int flags)
        {
            current = GetFirstNode();
            if (null != current)
            {
                time = GetNodeWindow(current).Begin;
                pos = SeekPos.Current;
                flags |= TWindowFlags.NodeChanged;
                return true;
            }
            time = -1;
            return false;
        }


        #region IEnumerable<E> Members

        IEnumerator<TWindow>  IEnumerable<TWindow>.GetEnumerator()
        {
            return _impl.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator  System.Collections.IEnumerable.GetEnumerator()
        {
 	        return _impl.GetEnumerator();
        }

        #endregion
    }

    public interface ITypeColumns
    {
        IList<IColumn> Columns { get; }
        Type Type { get; }
    }

    public class ColumnHistory<K, E> : IHistory<K,E>, ITypeColumns where K:IConvertible
    {
        
        internal TWindowCollection _windows = new TWindowCollection();
        protected IList<IColumn> _columns = Factory.NewList<IColumn>();
        protected IConverter<K, long> _keyConverter;
        protected Type _type;

        public IList<IColumn> Columns { get { return _columns; } }
        public Type Type { get { return _type; } }

        internal void PreUpdate(ColumnHistoryCursor<K, E> cursor)
        {
        }

        public IConverter<K, long> KeyConverter { get { return _keyConverter; } set { _keyConverter = value; } }


        public ColumnHistory(Type t, IEnumerable<FieldMeta> members, IConverter<K,long> conv)
        {
            _keyConverter = conv;
            Init(t, members);
        }

        #region IHistory<DateTime,E> Members

        public int Count
        {
            get { throw new NotImplementedException(); }
        }

        public Window<K> Range
        {
            get { throw new NotImplementedException(); }
        }

        public IList<Window<K>> Windows
        {
            get { throw new NotImplementedException(); }
        }

        public IHistCursor<K,E> GetCursor()
        {
           return  new ColumnHistoryCursor<K, E>(this, false);
        }

        public void Init(Type type, IEnumerable<FieldMeta> members)
        {
            _type = type;
            _columns = Factory.NewList<IColumn>();
            foreach (FieldMeta mi in members)
            {
                _columns.Add(new Column(mi));
            }
        }

        public IStream GetStream(ref object data)
        {
            if (null == data)
            {
                IDictionary<string, DataNode> map = Factory.NewDictionary<string,DataNode>();
                foreach (IColumn c in Columns)
                    map.Add(c.Name, c.NewNode());
                data = map;
            }
            return new ObjectStream(this, data as IDictionary<string,DataNode>);
        }

        #endregion

        #region IEnumerable<E> Members

        public IEnumerator<E> GetEnumerator()
        {
            return new ColumnHistoryCursor<K, E>(this, true);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion
    
    }

    public interface IConverter<A, B>
    {
        B Convert(A input);
        A Convert(B input);
    }

    internal class ColumnHistoryCursor<K, E> : IHistCursor<K, E> where K:IConvertible
    {
        private ColumnHistory<K, E> _history;

        private NTree.BinaryTree<NGenGen.Association<long, TWindow>> _node;
        private SeekPos _pos;

        private long _time;
        private int _flags;
        
        protected E _current;

        private IStream _stream;

        public ColumnHistoryCursor(ColumnHistory<K, E> history, bool readOnly)
        {
            _history = history;
            _node = null;
            _time = -1;
            _flags = readOnly ? TWindowFlags.ReadOnly : 0;
            Reset();
        }

        #region ICursor<E> Members

        public E  Current
        {
	        get 
	        {
                return _current;
	        }
            set 
	        {
                _current = value;
                //SetFlag(TWindowFlags.CurrentDirty, true);
                Write();
	        }
        }

        object ICursor.Current
        {
            get { return _current; }
            set { 
                _current = (E) value; 
                //SetFlag(TWindowFlags.CurrentDirty, true); 
                Write();
            }
        }

        private void SetFlag(int f, bool on)
        {
            if (on)
                _flags |= f;
            else
                _flags &= ~f;
        }

        private bool IsFlag(int f)
        {
            return (_flags & f) != 0 ? true : false;
        }

        public long CurrentStampID
        {
            get { return _time; }
            set { _time = value; }
        }

        public bool  MoveNext()
        {
            bool result = _history._windows.ForwardNode(ref _time, ref _node, ref _pos, ref _flags);
            if (result)
            {
                // check state
                if (_time < 0 || _node == null || _pos != SeekPos.Current)
                {
                    throw new InvalidOperationException();
                }

                CheckNewNode();

                Read();
            }
            return result;
        }

        protected void CheckNewNode()
        {
            if (IsFlag(TWindowFlags.NodeChanged))
            {
                TWindow wnd = _history._windows.GetNodeWindow(_node);

                _stream = _history.GetStream(ref wnd.Data);
            }
        }

        /// <summary>
        /// Reads current element from storage
        /// </summary>
        protected virtual void Read()
        {
            _current = (E)_stream.Read();
        }

        /// <summary>
        /// Writes current element to storage
        /// </summary>
        protected virtual void Write()
        {
            //if (IsFlag(TWindowFlags.CurrentDirty))
            {
                _stream.Write(_current);
            }
        }

        /// <summary>
        /// inserts new element before current element
        /// see table:
        /// current              result
        ///  pre-first, first       inserts at the beginning of coll
        ///  some                   inserts before some
        ///  last                   inserts before last
        ///  after-last             inserts at the end of coll
        /// </summary>
        public void  Insert()
        {
            if ((_flags & TWindowFlags.ReadOnly) != 0)
                throw new InvalidOperationException("Attempt to modify readonly history");
            
            bool result = _history._windows.ForwardNode(ref _time, ref _node, ref _pos, ref _flags);
            if (result)
            {
                CheckNewNode();
            }
            // here node is uptodate for _time

        }

        public void  Remove()
        {
 	        throw new NotImplementedException();
        }

        public void  Reset()
        {
            _node = null;
            _time = -1;
            _pos = SeekPos.BeforeFirst;
        }

        public int  Seek(SeekPos sp, int offset)
        {
 	        throw new NotImplementedException();
        }

        #endregion

        public K CurrentStamp
        {
            get
            {
                return _history.KeyConverter.Convert(CurrentStampID);
            }
            set
            {
                _time = _history.KeyConverter.Convert(value);
            }
        }

        #region IDisposable Members

        public void  Dispose()
        {
 	        throw new NotImplementedException();
        }

        #endregion

        #region IEnumerator Members

        object  System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        #endregion
    }

  
}